Двійкові дані та програма мікроконтролера
Досить часто виникає потреба додати двійкові дані до «прошивки» мікроконтролера. Це може бути знакогенератор для графічного дисплея чи принтера, закодована певним чином музика чи якась інша інформація, отримана у «двійковому» (тобто не-текстовому) вигляді від якоїсь «сторонньої» відносно компілятора для мікроконтролера програми.
У моєму випадку це теж прошивка, але для програмованої логіки (FPGA). Цю прошивку можна отримати у вигляді файлу .ttf (tabular text file, а не true type font :-)), у якому знаходяться десяткові числа, розділені комами.
Колись давно, ще «десь між i87c51FA та AT89C55» я з такого файлу для EPF8282 генерував asm-файл. Програмою sed додавав до та після масиву чисел потрібні заголовки з мітками, на початку кожного рядка директиву .DB і тому подібне. Асемблерний файл згодом компілювався в об’єктний та прилінковувався до програми.
Для ATmega162 та EP1K10 користувався власноруч написаною програмою — основна її робота була стиснути прошивку для альтерини простим, але ефективним алгоритмом, а вже видати назовні C-масив то була проста робота.
Тепер у мене LPC1766 та EP1C3. Циклони вже мають в собі декомпресор і квартус може стискати прошивки. Він це робить гірше, ніж алгоритм від Ivan Mak, але він це робить сам і розпаковує теж без мене. Тому я, принаймні зараз, повертаюся до простого перетворення стороннього файлу прошивки в об’єктний файл з масивом.
Зараз для таких робіт зазвичай пропонують вже готові програми на зразок bin2c для генерації C-шного масиву. До речі, на мою думку, однією з найкращих програм на тему все2всюди є пакет srecord.
Але при роботі з компіляторами gcc (точніше, з набором програм GNU binutils, яким користується і gcc) можна обійтися без додаткових програм, штатним інструментом з пакету. Програма objcopy може взяти «сирий» двійковий файл та конвертувати його безпосередньо в об’єктний, додавши мітки виду
_binary_XXX_end
_binary_XXX_size
де на місце XXX підставляється ім’я файлу (хех… повне ім’я, але про це нижче). Як в таких випадках прийнято, мітка _binary_XXX_end
вказує не на останній байт масиву, а на перший байт за масивом.
Цей метод теж часто радять на форумах, але в більшості місць забувають сказати, що за умовчанням масив створюється в секції .data
:
fpga_default.rbf fpga_default.o
$arm-kgp-eabi-objdump -t obj/fpga_default.o
SYMBOL TABLE:
00000000 l d .data 00000000 .data
00000000 g .data 00000000 _binary_fpga_default_rbf_start
000046d9 g .data 00000000 _binary_fpga_default_rbf_end
000046d9 g *ABS* 00000000 _binary_fpga_default_rbf_size
Десь це не важливо, наприклад, якщо програма зберігається у послідовній флеш-пам’яті та завантажується на старті в динамічну оперативну пам’ять.
Звісно, для AVR чи «однокристальних» систем на ARM таке розміщення не додає оптимізму, бо подібні масиви — константні. Їх би розмістити не в оперативній пам’яті, а у флеш, в саме для цього призначеній секції .rodata
(read-only data). Необхідний приклад (заміна імені та атрибутів секції) знаходиться в мануалі на програму objcopy.
--rename-section .data=.rodata,alloc,load,readonly,data,contents\
fpga_default.rbf fpga_default.o
$arm-kgp-eabi-objdump -t obj/fpga_default.o
obj/fpga_default.o: file format elf32-littlearm
SYMBOL TABLE:
00000000 l d .rodata 00000000 .rodata
00000000 g .rodata 00000000 _binary_fpga_default_rbf_start
000046d9 g .rodata 00000000 _binary_fpga_default_rbf_end
000046d9 g *ABS* 00000000 _binary_fpga_default_rbf_size
Все, об’єктний файл можна лінкувати до програми, використовуючи вказані імена. Наприклад, так (обробку помилок конфігурування прибрано, щоб не обтяжувати приклад зайвими деталями):
extern const uint8_t _binary_fpga_default_rbf_end[];
const uint8_t *pdata = _binary_fpga_default_rbf_start;
fpga_load_start();
do {
fpga_load_byte(*pdata++);
} while(pdata < _binary_fpga_default_rbf_end);
Все було б непогано, але імена і так довгі, та ще й при використанні «автоматизованого» Makefile, в якому автоматично формуються переліки об’єктних файлів з різних підкаталогів проекту та використовуються правила-зразки, в правилі
%.o: %.rbf
arm-kgp-eabi-objcopy -I binary -O elf32-littlearm -B arm\
--rename-section .data=.rodata,alloc,load,readonly,data,contents $< $@
автоматична змінна $<
розгорнеться в ім’я ./src/spectrom3/fpga_default.rbf
з якого objcopy згенерує _binary___src_spectrom3_fpga_default_rbf_start
, що далеко виходить за межі моєї терплячки.
На щастя, objcopy має гарний ключ --redefine-sym old=new
. При генерації файлу fpga_default.o досить додати три ключі:
--redefine-sym _binary___src_spectrom3_fpga_default_rbf_end=fpga_default_end
--redefine-sym _binary___src_spectrom3_fpga_default_rbf_size=fpga_default_size
В програмі можна буде використати коротші імена. Але… Не додавати ж вручну ці ключі до кожного rbf-файлу (та ще й створюючи для кожного з них персональні правила для обробки!). Доведеться кликати на допомогу функції GNU make для обробки імен файлів та для обробки тексту. Як видно з прикладів вище, objcopy заміняє в імені файлу всі символи, які не можуть бути присутніми в ідентифікаторах, на символ підкреслення та додає _binary_ . Зробимо ту ж саму підстановку для імені символу, який треба замінити:
З символом, який я хочу отримати, легше. Це має бути просто ім’я файлу, без шляху та розширення.
Повне правило виглядає наступним чином:
$(OBJDIR)/%.o: %.rbf Makefile
@echo --- converting $< ...
$(OBJCOPY) -I binary -O elf32-littlearm -B arm \
--redefine-sym _binary_$(subst /,_,$(subst .,_,$<))_start=$(notdir $(basename $<))_start \
--redefine-sym _binary_$(subst /,_,$(subst .,_,$<))_end=$(notdir $(basename $<))_end \
--redefine-sym _binary_$(subst /,_,$(subst .,_,$<))_size=$(notdir $(basename $<))_size \
--rename-section .data=.rodata,alloc,load,readonly,data,contents $< $@
Тепер десь в процесі роботи make на екрані буде рядок:
Проте в об’єктному файлі будуть зручні короткі імена:
obj/fpga_default.o: file format elf32-littlearm
SYMBOL TABLE:
00000000 l d .rodata 00000000 .rodata
00000000 g .rodata 00000000 fpga_default_start
000046d9 g .rodata 00000000 fpga_default_end
000046d9 g *ABS* 00000000 fpga_default_size
© 2012, Олександр Редчук aka ReAl